package cz.zcu.sefler.mois.business;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.MasonTagTypes;
import net.htmlparser.jericho.MicrosoftTagTypes;
import net.htmlparser.jericho.PHPTagTypes;
import net.htmlparser.jericho.Source;
import cz.zcu.sefler.mois.data.model.Monitor;

/**
 * Tato trida projde rubriku rozhovory serveru super.cz, ziska adresy
 * jednotlivych rozhovoru a vytvori vlakna, ktera tyto rozhovory zpracuji.
 * 
 * @author Vojtech Sefler
 * 
 */
@SuppressWarnings("deprecation")
public class SuperCzArchiveSpiderAndDirigentThread extends Thread {
  private String        dbMachine;
  private String        dbDb;
  private String        dbUser;
  private String        dbPass;

  /* Adresy rozhovoru. */
  LinkedHashSet<String> urlSet                  = new LinkedHashSet<String>();

  /*
   * Adresa aktualni stranky rozhovoru, ze ktere ziskavame adresy jednotlivych
   * rozhovoru.
   */
  String                currentPageUrl;

  /* Maximalni pocet soucasne zpracovavanych rozhovoru. */
  private final int     MAX_RUNNING_MONITORS    = 5;

  /* Adresa prvni strany rozhovoru. */
  private final String  TOP_ARCHIVE_URL         = "http://www.super.cz/archiv?sectionId=39";

  /* Prefix dalsi strany rozhovoru. */
  private final String  ARCHIVE_PAGE_URL_PREFIX = "&page=";

  /* Cislo prvni strany archivu rozhovoru. */
  private final int     FIRST_ARCHIVE_PAGE      = 2;

  /* Aktualni cislo stranky archivu rozhovoru. */
  int                   pageNo                  = FIRST_ARCHIVE_PAGE;

  public SuperCzArchiveSpiderAndDirigentThread(String dbMachine, String dbDb, String dbUser, String dbPass) {
    super();
    this.pageNo = FIRST_ARCHIVE_PAGE;
    this.dbMachine = dbMachine;
    this.dbDb = dbDb;
    this.dbUser = dbUser;
    this.dbPass = dbPass;
    this.currentPageUrl = TOP_ARCHIVE_URL;
  }

  /**
   * Metoda vrati pocet bezicich vlaken, ktere zpracovavaji jednotlive
   * rozhovory.
   * 
   * @param runningMonitors
   *          Seznam bezicich vlaken, ktere zpracovavaji rozhovory.
   * @return Pocet pracovnich vlaken.
   */
  private int getRunningThreadsNumber(ArrayList<GeneralMonitorThread> runningMonitors) {
    int count = 0;
    for (GeneralMonitorThread superCzMonitorThread : runningMonitors) {
      /* Pokud je stav vlakna RUNNABLE, zvysim pocet bezicich vlaken. */
      if (superCzMonitorThread.getState() == State.RUNNABLE) {
        count++;
      }
    }
    return count;
  }

  private void joinAllMonitorThreads(ArrayList<GeneralMonitorThread> beziciMonitory) {
    for (GeneralMonitorThread superCzMonitorThread : beziciMonitory) {
      try {
        superCzMonitorThread.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * Metoda vrati adresu nasledujici stranky.
   * 
   * Adresy nasleduji v tomto poradi:
   * 
   * http://www.super.cz/archiv?sectionId=39
   * http://www.super.cz/archiv?sectionId=39&page=2
   * http://www.super.cz/archiv?sectionId=39&page=3
   * ...
   */
  private void getNextPageURL() {
    this.currentPageUrl = TOP_ARCHIVE_URL + ARCHIVE_PAGE_URL_PREFIX + Integer.toString(pageNo++);
  }

  /**
   * Metoda ze stranky archivu ziska adresy jednotlivych rozhovoru a vrati je v
   * seznamu.
   * 
   * @param topUrl
   *          Stranka archivu, kde se nalezaji adresy jednotlivych rozhovoru.
   * @return List adres rozhovoru z dane stranky.
   */
  private LinkedHashSet<String> getSeparatedUrls(String topUrl) {
    /*
     * Set obsahuje unikatni hodnoty, proto duplicitni odkazy na rozhovory
     * nebudou brany v uvahu. LinkedHashSet navic zajisti poradi adres, v jakem
     * jsme jej do seznamu zadali.
     */
    LinkedHashSet<String> newUrlSet = new LinkedHashSet<String>();

    /* Nastaveni Jericho parseru. */
    MicrosoftTagTypes.register();
    PHPTagTypes.register();
    PHPTagTypes.PHP_SHORT.deregister();
    MasonTagTypes.register();

    Source source;
    try {
      source = new Source(new URL(topUrl));
      /* Parsovani stranky na jednotlive elementy. */
      source.fullSequentialParse();

      /*
       * Jednotlive rozhovory se nachazeji v tomto bloku:
       * <div id="nextstories" class="archive">
       */
      Element ns = source.getElementById("nextstories");

      /* Stranka za posledni strankou archivu nevrati zadne nove adresy. */
      if (ns == null) {
        return newUrlSet;
      }

      /* Ziskame vsechny <a..> elementy. */
      List<Element> lst = ns.getAllElements("a");

      for (Element element : lst) {
        /*
         * V elementu <div id="nextstories"> se nachazi jeden odkaz navic, ktery
         * odkazuje na nasledujici stranku. Tento odkaz neobsahuje rozhovor,
         * proto jej neulozime do seznamu adres rozhovoru. Je identifikovan
         * neprazdnym atributem class.
         */
        if (element.getAttributeValue("class") == null) {
          newUrlSet.add(element.getAttributeValue("href"));
        }
      }

      return newUrlSet;
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return null;
  }

  /**
   * Pracovni metoda vlakna. Nejdrive nacte vsechny adresy rozhovoru na serveru
   * super.cz. Pote kazdy rozhovor zpracuje zvlast instanci tridy
   * SuperCzMonitorThread. Udrzuje rozumny pocet techto instanci bezicich
   * paralelne.
   */
  @Override
  public void run() {
    /* Seznam bezicich instanci pracujicich vlaken. */
    ArrayList<GeneralMonitorThread> runningMonitors = new ArrayList<GeneralMonitorThread>();

    /*
     * Ziskani adres rozhovoru na dane strance archivu. Cyklus bude probihat,
     * dokud kazda nasledujici strana archivu bude obsahovat nove odkazy
     * rozhovoru. Jinak receno, metoda addAll vraci true, pokud objekt urlSet
     * byl touto metodou zmenen (pridan alespon jeden novy prvek). V parametru
     * metody addALL je seznam adres rozhovoru na aktualni archivni strance.
     */
    while (urlSet.addAll(getSeparatedUrls(this.currentPageUrl))) {
      getNextPageURL();
    }

    /*
     * Kazde adrese rozhovoru vytvorim vlastni pracovni vlakno, ktere rozhovor
     * zpracuje.
     */
    for (String url : urlSet) {
      /* Pokud je vice pracujicich vlaken nez parametr... */
      while (getRunningThreadsNumber(runningMonitors) > MAX_RUNNING_MONITORS) {
        try {
          /* ...uspim tuto instanci. */
          Thread.sleep(50);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      /* Vytvorim nove pracujici vlakno, ktere zpracuje rozhovor. */
      GeneralMonitorThread newMonitorThread;
      newMonitorThread = new SuperCzMonitorThread(new Monitor(url), dbMachine, dbDb, dbUser, dbPass);

      /* Pridani vlakna do seznamu. */
      runningMonitors.add(newMonitorThread);
      newMonitorThread.start();
    }

    /* Pockame, az vsechny vlakna dodelaji svou praci. */
    joinAllMonitorThreads(runningMonitors);
  }
}
